/*
 * This file is part of Ember.
 *
 * Ember is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ember is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ember.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) Grant Tyra 2010
 * granttyra@gmail.com
*/

#include "ember_call_stack.h"
#include "ember_string.h"
#include "ember_types.h"
#include <stdlib.h>

EmberList *emberCallStack = NULL;

void EmberCallStackInitialize() {
    EmberStackFrame *global = EmberStackFrameCreate();
	emberCallStack = EmberListCreate();
	EmberCallStackPush(global);
}

void EmberCallStackDestroy() {
    EmberStackFrame *frame = NULL;

    while ((frame = EmberCallStackPop()) != NULL) {
        EmberStackFrameDestroy(frame);
    }
    
    EmberListDestroy(emberCallStack);
    emberCallStack = NULL;
}

EmberStackFrame *EmberStackFrameCreate() {
	EmberStackFrame *frame = malloc(sizeof(EmberStackFrame));
	frame->symbols = EmberDictionaryCreate();
	frame->classdef = NULL;
	return frame;
}

void EmberStackFrameDestroy(EmberStackFrame *frame) {
    int i = 0;
	EmberObject *obj = NULL;
    EmberList *values = NULL;

    values = EmberListCreate();
	EmberDictionaryValues(frame->symbols, values);

	for (i = 0; i < values->size; i++) {
        obj = EmberListGet(values, i);
        EmberObjectRelease(obj);
    }

	EmberListDestroy(values);
	EmberDictionaryDestroy(frame->symbols);
	free(frame);
}

void EmberStackFrameBind(EmberStackFrame *frame, const char *name, EmberObject *obj) {
	EmberObject *old = EmberDictionaryGet(frame->symbols, name);

	if (old != NULL) {
		EmberObjectRelease(old);
	}

	EmberDictionaryPut(frame->symbols, name, obj);
    EmberObjectRetain(obj);
}

EmberObject *EmberCallStackRetrieve_local(const char *name) {
	EmberStackFrame *frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
	EmberObject *obj = EmberDictionaryGet(frame->symbols, name);

	if (obj == NULL) {
		return EmberNull();
	}

	return obj;
}

EmberObject *EmberCallStackRetrieve_instance(const char *name) {
	EmberStackFrame *frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
	EmberObject *ths = EmberDictionaryGet(frame->symbols, "this");
	EmberObject *attrib = NULL;

	if (ths == NULL) {
		return EmberObjectCreateException("Could not retrieve 'this' object.");
	}

	attrib = EmberObjectGetAttribute(ths, name);

	if (attrib == NULL) {
		return EmberNull();
	}

	return attrib;
}

EmberObject *EmberCallStackRetrieve_static(const char *name) {
	EmberStackFrame *frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
	EmberObject *attrib = EmberClassGetAttribute(frame->classdef, name);

	if (attrib == NULL) {
		return EmberNull();
	}

	return attrib;
}

EmberObject *EmberCallStackRetrieve(const char *name) {
	char scopeid = name[0];

	if (scopeid == '@') {
		return EmberCallStackRetrieve_instance(name);
	}
	
	if (scopeid == '$') {
		return EmberCallStackRetrieve_static(name);
	}
	
	return EmberCallStackRetrieve_local(name);
}

void EmberCallStackBind_local(const char *name, EmberObject *value) {
	EmberStackFrame *frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
	EmberObject *old = EmberDictionaryGet(frame->symbols, name);

	if (old != NULL) {
		EmberObjectRelease(old);
	}

	EmberDictionaryPut(frame->symbols, name, value);
}

void EmberCallStackBind_instance(const char *name, EmberObject *value) {
	EmberObject *ths = EmberCallStackRetrieve_local("this");
	EmberObject *old = EmberObjectGetAttribute(ths, name);

	if (old != NULL) {
		EmberObjectRelease(old);
	}

	EmberObjectPutAttribute(ths, name, value);
}

void EmberCallStackBind_static(const char *name, EmberObject *value) {
	EmberStackFrame *frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
	EmberObject *old = EmberCallStackRetrieve_static(name);

	if (old != NULL) {
		EmberObjectRelease(old);
	}

	EmberClassPutAttribute(frame->classdef, name, value);
}

void EmberCallStackBind(const char *name, EmberObject *value) {
	char scopeid = name[0];

	if (scopeid == '@') {
		EmberCallStackBind_instance(name, value);
	}
	
	if (name[0] == '$') {
		EmberCallStackBind_static(name, value);
	}
	
	EmberCallStackBind_local(name, value);
	EmberObjectRetain(value);
}

void EmberCallStackPush(EmberStackFrame *frame) {
	EmberListAdd(emberCallStack, frame);
}

EmberStackFrame *EmberCallStackPop() {
	EmberStackFrame *frame = NULL;
	
	if (emberCallStack->size > 0) {
		frame = EmberListGet(emberCallStack, emberCallStack->size - 1);
		EmberListRemove(emberCallStack, emberCallStack->size - 1);	
	}

	return frame;
}
